/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package jps_s10996_s7175.envs;

import java.util.LinkedList;

import jps_s10996_s7175.objects.ComplexObject;
import jps_s10996_s7175.result.BagResult;
import jps_s10996_s7175.result.ReferenceResult;
import edu.pjwstk.jps.datastore.IOID;
import edu.pjwstk.jps.datastore.ISBAObject;
import edu.pjwstk.jps.datastore.ISBAStore;
import edu.pjwstk.jps.interpreter.envs.IENVS;
import edu.pjwstk.jps.interpreter.envs.IENVSBinder;
import edu.pjwstk.jps.interpreter.envs.IENVSFrame;
import edu.pjwstk.jps.result.IAbstractQueryResult;
import edu.pjwstk.jps.result.IBagResult;
import edu.pjwstk.jps.result.ISingleResult;
import jps_s10996_s7175.objects.ReferenceObject;
import jps_s10996_s7175.objects.SBAObject;
import jps_s10996_s7175.objects.SBAStore;
import jps_s10996_s7175.result.BinderResult;
import jps_s10996_s7175.result.SingleResult;
import jps_s10996_s7175.result.StructResult;

/**
 *
 * @author Tomasz & Kamil
 */
public class ENVS implements IENVS {

    private final LinkedList<IENVSFrame> stackENVS;

    public ENVS() {
        this.stackENVS = new LinkedList<IENVSFrame>();
    }

    @Override
    public void init(IOID rootOID, ISBAStore store) {
        ISBAObject rootObject = store.retrieve(rootOID);
        LinkedList<IENVSBinder> binderList = new LinkedList<IENVSBinder>();

        //System.out.println(rootObject.getName());

        if (rootObject instanceof ComplexObject) {
            ISBAObject childObject;
            for (IOID childID : ((ComplexObject) rootObject).getChildOIDs()) {
                childObject = store.retrieve(childID);

                binderList.add(new ENVSBinder(childObject.getName(),
                        new ReferenceResult(childObject.getOID())));
            }
        } else { // Simple object or referenceObject
            binderList.add(new ENVSBinder(rootObject.getName(),
                    new ReferenceResult(rootObject.getOID())));
        }
        
        push(new ENVSFrame(binderList));
    }

    @Override
    public synchronized IENVSFrame pop() {
        return this.stackENVS.pollLast(); // zwroc ostatni i zdejmij go ;)
    }

    @Override
    public synchronized void push(IENVSFrame frame) {
        this.stackENVS.add(frame);
    }

    @Override
    public synchronized IBagResult bind(String name) {

        BagResult oResult = new BagResult();

        java.util.Iterator<IENVSFrame> oIter = stackENVS.descendingIterator();
        ENVSFrame oFrame;
        boolean bFound = false;

		// iterate over whole stack from the top to the bottom
        // unless you have already found name you were looking for
        while (oIter.hasNext() && !bFound) {
            oFrame = (ENVSFrame) oIter.next();
            for (IENVSBinder oBinder : oFrame.getElements()) {
                if (name.equals(oBinder.getName())) {
                    bFound = true;
                    oResult.add((ISingleResult) oBinder.getValue());
                }
            }
        }

        return oResult;

    }

    @Override
    public IENVSFrame nested(IAbstractQueryResult result, ISBAStore store) {
        LinkedList<IENVSBinder> binderList = new LinkedList<IENVSBinder>();
        if ( result instanceof ReferenceResult ) {
            SBAObject<?> nestedOBJ = (SBAObject<?>)store.retrieve(((ReferenceResult) result).getOIDValue());
            if ( nestedOBJ instanceof ComplexObject ) {
                //1. Dla referencji do obiektu złożonego: zbiór zawierający bindery z nazwami i
                //identyfikatorami (ReferenceResult) podobiektów tego obiektu
                
                for ( IOID ioid : ((ComplexObject) nestedOBJ).getChildOIDs()) {
                    SBAObject<?> objInner = (SBAObject<?>)store.retrieve(ioid);
                    binderList.add(new ENVSBinder(
                                objInner.getName(),
                                new ReferenceResult(objInner.getOID())
                            ));
                }
                
            } else if ( nestedOBJ instanceof ReferenceObject ) {
                //2. Dla referencji do obiektu referencyjnego: zbiór zawierający binder z nazwą i
                //identyfikatorem (ReferenceResult) obiektu wskazywanego
                ENVSBinder binder = new ENVSBinder(
                        store.retrieve(nestedOBJ.getOID()).getName(),
                        new ReferenceResult(nestedOBJ.getOID())
                        );
                binderList.add(binder);
            } else if ( SBAStore.isSimpleType(nestedOBJ) ) {
                //6. Dla referencji do obiektu prostego: pusty zbiór
                // nothing
            }
        } else if ( result instanceof BinderResult ) {
            //3. Dla bindera: ten sam binder
            BinderResult obj = (BinderResult) result;
            ENVSBinder binder = new ENVSBinder(
                    obj.getName(),
                    obj.getValue()
                    );
            binderList.add(binder);
        } else if ( result instanceof StructResult ) {
            //4. Dla struktury: zbiór będący sumą operacji nested wszystkich elementów struktury
            StructResult obj = (StructResult) result;
            for (ISingleResult single : obj.elements()) {
                SingleResult item = (SingleResult) single;
                ENVSFrame innerFrame = (ENVSFrame) nested(item, store);
                binderList.addAll(innerFrame.getElements());
            }
        } else if ( SBAStore.isSimpleType(result) ) {
            //5. Dla wartości atomowej: pusty zbiór
            // nothing
        } else {
            //7. Dla pozostałych rezultatów: pusty zbiór
            // nothing
        }
        return new ENVSFrame(binderList);
    }
    
    @Override
    public String toString(){
        return stackENVS + "";
    }
}
